////////////////////////////////////////////////////////////////////////////////
// Copyright 2010 Michael Schmalle - Teoti Graphix, LLC
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and 
// limitations under the License
// 
// Author: Michael Schmalle, Principal Architect
// mschmalle at teotigraphix dot com
////////////////////////////////////////////////////////////////////////////////
package fxplugins.utility
{

import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;

import fxplugins.core.IComponentMap;
import fxplugins.core.IComponentMapAware;
import fxplugins.core.IDisposable;

import mx.core.IMXMLObject;

[Event(name="utilityAdd", type="fxplugins.utility.UtiltiyMapEvent")]

[Event(name="utilityRemove", type="fxplugins.utility.UtiltiyMapEvent")]

[DefaultProperty("utilities")]

/**
 * The UtilityMap is a concrete implementation of the <code>IUtilityMap</code> 
 * API.
 * 
 * <p>The UtilityMap is the central location for all Aries platform utilities.
 * An Aries utility consists of a Class type API and an instance that has 
 * implemented that API.</p>
 * 
 * <p>There are two ways to add a utility to the map.</p>
 * <ol>
 * <li>Declare the API and utility instance in the MXML MyUtilityMap.mxml
 * file.</li>
 * <li>Use the <code>putUtility()</code> method of the UtilityMap class.</li>
 * </ol>
 * 
 * @author Michael Schmalle
 * @copyright Teoti Graphix, LLC
 * @productversion 1.0
 */
public class UtilityMap extends EventDispatcher 
	implements IMXMLObject, IUtilityMap, IDisposable
{
	//----------------------------------
	//  componentMap
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _componentMap:IComponentMap;
	
	/**
	 * TODO Docme
	 */
	public function get componentMap():IComponentMap
	{
		return _componentMap;
	}
	
	/**
	 * @private
	 */	
	public function set componentMap(value:IComponentMap):void
	{
		if (_componentMap == value)
			return;
		
		_componentMap = value;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private :: Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private static var _map:Dictionary;
	
	/**
	 * @private
	 */
	private var utilitiesInitialized:Boolean = false;
	
	//--------------------------------------------------------------------------
	//
	//  Public :: Properties
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private var _utilities:Array = [];
	
	[ArrayElementType("fxplugins.utility.Utility")]
	
	/**
	 * Sets the initial utility map using an array.
	 * 
	 * <p>This property is to be used in MXML startup code.</p>
	 */
	public function get utilities():Array
	{
		return _utilities;
	}
	
	/**
	 * @private
	 */
	public function set utilities(value:Array):void
	{
		_utilities = value;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * @private
	 */
	public function UtilityMap()
	{
		super();
		
		_map = new Dictionary(true);
	}
	
	//--------------------------------------------------------------------------
	//
	//  IMXMLObject API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	public function initialized(document:Object, id:String):void
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  IUtilityMap API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @copy fxplugins.utility.IUtilityMap#getUtility()
	 */
	public function getUtility(key:Class):Object
	{
		var info:UtilityInfo = null;
		var service:Object = null;
		
		if (_map != null)
		{
			info = _map[key];
			
			if (info != null)
				return info.utility;
		}
		
		return null;
	}
	
	/**
	 * @copy fxplugins.utility.IUtilityMap#putUtility()
	 */
	public function putUtility(type:Class, utility:Object):Object
	{
		if (type == null)
		{
			throw Error("The utility type cannot be null");
		}
		else if (utility != null && !(utility is type))
		{
			throw Error("The utility is not an instance of " + type);
		}
		
		if (_map[type] != null || (_map[type] != null && utility == null))
		{
			return removeUtility(type);
		}
		
		if (utility != null)
		{
			addUtility(type, utility);
		}
		
		return utility;
	}
	
	/**
	 * @copy fxplugins.utility.IUtilityMap#hasUtility()
	 */
	public function hasUtility(key:Class):Boolean
	{
		if (_map[key])
		{
			return true;
		}
		
		return false;
	}
	
	//--------------------------------------------------------------------------
	//
	//  IDisposable API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @copy com.teotigraphix.core.IDisposable#dispose()
	 */
	public function dispose():void
	{
		for each (var service:UtilityInfo in _map)
		{
			removeUtility(service.type);
		}
		
		_utilities = null;
	}
	
	/**
	 * @private
	 */
	private function addUtility(type:Class, utility:Object):void
	{
		_map[type] = new UtilityInfo(type, utility);
		
		if (utility is IComponentMapAware)
			IComponentMapAware(utility).componentMap = _componentMap;
		
		if (utilitiesInitialized)
		{
			dispatchUtilityAdd(type, utility);
		}
	}
	
	/**
	 * @private
	 */
	private function removeUtility(type:Class):Object
	{
		var oldUtility:UtilityInfo = _map[type];
		
		delete _map[type];
		
		if (oldUtility.utility is IDisposable)
			IDisposable(oldUtility.utility).dispose();
		
		if (oldUtility is IComponentMapAware)
			IComponentMapAware(oldUtility).componentMap = null;
		
		if (utilitiesInitialized)
		{
			dispatchUtilityRemove(type, oldUtility.utility);
		}
		
		return oldUtility.utility;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 * Called when this instance is being constructed.
	 * 
	 * All utilities will be registered first, then after all initial utilities
	 * have been added to this map, the locator will dispatch a utilityAdd
	 * event.
	 */
	public function initialize():void
	{
		if (utilitiesInitialized)
			return;
		
		for (var i:int = 0; i < _utilities.length; i++)
		{
			var element:Utility = _utilities[i] as Utility;
			
			if (element.type != null && element.instance == null)
			{
				if (element.provider != null)
				{
					var provider:IUtilityProvider = new element.provider() as IUtilityProvider;
					if (provider != null)
					{
						element.instance = provider.getInstance();
					}
				}
				else if (element.generator != null)
				{
					element.instance = new element.generator();
				}
			}
			
			if (element.type != null && element.instance != null)
			{
				putUtility(element.type, element.instance);
			}
		}
		
		var info:UtilityInfo = null;
		
		for each (info in _map)
		{
			if (info.type != null && info.utility != null)
			{
				dispatchUtilityAdd(info.type, info.utility);
			}
		}
		
		utilitiesInitialized = true;
	}
	
	/**
	 * @private
	 */
	private function dispatchUtilityAdd(type:Class, service:Object):void
	{
		var e:UtilityMapEvent = new UtilityMapEvent(
			UtilityMapEvent.UTILITY_ADD, type, service);
		
		if (hasEventListener(UtilityMapEvent.UTILITY_ADD))
		{
			dispatchEvent(e);
		}
		
		if (service is IUtility)
		{
			var dispatcher:IEventDispatcher = service as IEventDispatcher;
			
			if (dispatcher && dispatcher.hasEventListener(
				UtilityMapEvent.UTILITY_ADD))
			{
				dispatcher.dispatchEvent(e);
			}
		}
	}
	
	/**
	 * @private
	 */
	private function dispatchUtilityRemove(type:Class, utility:Object):void
	{
		var e:UtilityMapEvent = new UtilityMapEvent(
			UtilityMapEvent.UTILITY_REMOVE, type, utility);
		
		if (hasEventListener(UtilityMapEvent.UTILITY_REMOVE))
		{
			dispatchEvent(e);
		}
		
		if (utility is IUtility)
		{
			var dispatcher:IEventDispatcher = utility as IEventDispatcher;
			
			if (dispatcher && dispatcher.hasEventListener(
				UtilityMapEvent.UTILITY_REMOVE))
			{
				dispatcher.dispatchEvent(e);
			}
		}
	}
}
}

class UtilityInfo
{
	public var type:Class;
	public var utility:Object;
	
	public function UtilityInfo(type:Class, utility:Object)
	{
		this.type = type;
		this.utility = utility;
	}
}